package com.fduss.mazegame.server;

import com.fduss.mazegame.core.Update;
import com.fduss.mazegame.core.maze.Map;
import com.fduss.mazegame.core.maze.Maze;
import com.fduss.mazegame.core.unit.End;
import com.fduss.mazegame.core.unit.Hero;
import com.fduss.mazegame.core.unit.Monster;
import com.fduss.mazegame.util.Direction;
import com.fduss.mazegame.util.Queue;
import com.fduss.mazegame.util.Stack;
import com.fduss.mazegame.util.StackArray;

import java.util.EmptyStackException;

/**
 * Facade Pattern: A server is the main tool of the client.
 * <p>
 * The server will be created by passing it some ServerSpecs,
 * generated by CLOptions processing the command line arguments.
 * <p>
 * The server will accept the hero movements invoked by the client
 * and pass them to the maze.
 * <p>
 * The server will also provide the client with the ClientView, which
 * is all the client needs to rendering the game status to the
 * player.
 *
 * @Date 2017/9/27
 * @Time 16:42
 */
public class Server {

    protected Maze maze;

    protected Stack<Direction> undo;

    protected Hero hero;

    private ServerSpec spec;

    public Server(ServerSpec spec) {
        this.spec = spec;
    }

    private static Direction reverse(Direction d) {
        switch (d) {
            case NORTH:
                return Direction.SOUTH;
            case SOUTH:
                return Direction.NORTH;
            case WEST:
                return Direction.EAST;
            case EAST:
                return Direction.WEST;
            default:
                throw new UnsupportedOperationException(
                        "Unsupported Direction: " + d);
        }
    }

    public Server init() {
        Map map = spec.generateMap();
        End end = spec.generateEnd();
        hero = spec.generateHero();
        Monster[] monsters = spec.generateMonsters();
        int trailCapacity = spec.generateTrailCapacity();
        this.maze = new Maze(map, end, hero, monsters, trailCapacity);
        undo = new StackArray<>();
        return this;
    }

    /**
     * move the player one step to in the given direction
     *
     * @param dir Direction
     * @return
     */
    public boolean moveHero(Direction dir) {
        boolean ok = maze.moveHero(dir);
        if (ok)
            undo.push(reverse(dir));
        return ok;
    }

    public ClientView getClientView() {
        return maze.getClientView();
    }

    public Queue<Update> getUpdates() {
        return maze.getUpdates();
    }

    public boolean isGameOver() {
        return maze.isGameOver();
    }

    public boolean isHeroAlive() {
        return maze.isHeroAlive();
    }

    public boolean undo() {
        Direction d;
        try {
            d = undo.pop();
        } catch (EmptyStackException ex) {
            return false;
        }
        maze.moveHero(d);
        return true;
    }

    /**
     * kill all the monsters around the player
     *
     * @return
     */
    public boolean killMonster() {
        boolean result = false;
        for (Direction d : Direction.values()) {
            if (maze.killMonster(d))
                result = true;
        }
        return result;
    }

    public boolean pickTreasure() {
        boolean result = false;
        for (Direction d : Direction.values()) {
            if (maze.pickTreasure(d))
                result = true;
        }
        return result;
    }

    public Hero checkScore() {
        return maze.checkScore();
    }
}
